package com.xgimi.open.skill.datamanager.biz.common.redis;

import com.xgimi.open.skill.datamanager.biz.constant.NluConstants;
import com.xgimi.open.skill.datamanager.biz.utils.RedisKeyBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
 * <b>描述：</b> 基于Redis的简单分布式锁. 针对特殊异常场景, 因此要求对锁的每一个Key,
 * 需要设置锁的过期时间以确保锁的失效. <br/>
 */
@Service
@Slf4j
public class RedisLockService {

    @Autowired
    private RedisService redisService;

    private static final String REDIS_LOCK_VAL = "1";

    private static final int LOCK_INTERVAL = 250;

    /**
     * <b>描述：</b> 尝试获取锁 <br/>
     *
     * @param key           - 锁的键
     * @param expireSeconds - 锁的安全过期时间, 秒
     * @return 锁成功或失败
     */
    public boolean tryLock(String key, int expireSeconds) {
        return redisService.setnx(key, REDIS_LOCK_VAL) && redisService.expire(key, expireSeconds) > 0;
    }

    /**
     * <b>描述：</b> 尝试获取锁 <br/>
     * <b>修改内容：</b>  <br/>
     *
     * @param key           - 锁的键
     * @param expireSeconds - 锁的安全过期时间, 秒
     * @param waitMills     - 重试等待次数, 毫秒
     * @return 锁成功或失败
     */
    public boolean lock(String key, int expireSeconds, int waitMills) {
        int num = (int) Math.ceil(waitMills / (double) LOCK_INTERVAL);
        final Object lockObj = new Object();

        //lock try
        if (tryLock(key, expireSeconds)) {
            return true;
        }

        //重试
        for (int i = 1; i < num; i++) {
            try {
                //wait interval and check
                synchronized (lockObj) {
                    lockObj.wait(LOCK_INTERVAL);
                }
            } catch (InterruptedException e) {
                return false;
            }

            if (tryLock(key, expireSeconds)) {
                return true;
            }
        }
        return false;
    }

    /**
     * <b>描述：</b> 释放锁 <br/>
     *
     * @param key - 锁的键
     * @return 释放成功或失败
     */
    public boolean unlock(String key) {
        Long del = redisService.del(key);
        return del != null && del > 0;
    }


    public String isCanBePublish(Long skillId) {
        String skillPublishLockKey = RedisKeyBuilder.buildNluSkillPublishKey(skillId.toString());
        boolean locked = this.lock(skillPublishLockKey, NluConstants.NLU_LOCK_EXPIRE_TTS, NluConstants.NLU_LOCK_WAIT_TIME_MILLS);
        if (!locked) {
            log.warn("获得Skill的发布锁失败, 当前Skill正在版本, 请稍后重试! Skill:{}", skillId);
            return "";
        }
        return skillPublishLockKey;
    }

    /**
     * 判断当前技能是否正在被发布
     *
     * @return
     */
    public Boolean isSkillPublishing(Long skillId) {
        String skillPublishLockKey = RedisKeyBuilder.buildNluSkillPublishKey(skillId.toString());
        String skillPublishLockValue = redisService.get(skillPublishLockKey);
        return !Objects.isNull(skillPublishLockValue);
    }

    /**
     * 判断当前技能是否正在被训练
     *
     * @return
     */
    public Boolean isSkillTraining(Long skillId) {
        String skillTrainLockKey = RedisKeyBuilder.buildNluTrainLockKey(skillId.toString());
        String skillTrainLockValue = redisService.get(skillTrainLockKey);
        return !Objects.isNull(skillTrainLockValue);
    }

    public Boolean isCanNotUpdate(Long skillId) {
        return this.isSkillPublishing(skillId) || this.isSkillTraining(skillId);
    }

}
